package cache_def;  

//cache parameter
parameter int ADDR_WIDTH = 32;
parameter int DATA_WIDTH = 64;
parameter int CACHE_SIZE = 4 * 1024 * 8;
parameter int LINE_WIDTH = 128;
parameter int SET_ASSOC = 4;
parameter int INDEX_NUM = CACHE_SIZE / (LINE_WIDTH * SET_ASSOC);
parameter int INDEX_WIDTH = $clog2(INDEX_NUM);
parameter int LINE_BYTE_OFFSET = $clog2(LINE_WIDTH/8);
parameter int TAGMSB = ADDR_WIDTH-1;    //tag msb  
parameter int TAGLSB = INDEX_WIDTH + LINE_BYTE_OFFSET;    //tag lsb  


//128-bit cache line data  
typedef bit [LINE_WIDTH-1:0] cache_data_type;

//----------------------------------------------------------------------  
// data structures for CPU<->Cache controller interface  

// CPU request (CPU->cache controller)  
typedef struct {    
    bit [ADDR_WIDTH-1:0] addr;            //request addr    
    bit [DATA_WIDTH-1:0] data;            //request data (used when write)
    bit [DATA_WIDTH/8-1:0] wstrb;         //data byte mask (used when write)
    bit rw;                               //request type : 0 =read,1=write                     
    bit valid;                            //request  is  valid  
}cpu_req_type;  

// Cache result (cache controller->cpu)  
typedef struct {    
    bit  [DATA_WIDTH-1:0] data;            //32-bit  data    
    bit ready;                 //result is ready  
}cpu_result_type;  

//----------------------------------------------------------------------  
// data structures for cache controller<->memory interface  

// memory request (cache controller->memory)  
typedef struct {    
    bit [31:0]addr;            //request byte addr    
    bit [127:0]data;           //128-bit request data (used when write)
    bit rw;                     //request type : 0 =read,1=write                     
    bit valid;                 //request  is  valid 
}mem_req_type;  

// memory controller response (memory -> cache controller)  
typedef struct {    
    cache_data_type data;      //128-bit read back data    
    bit    ready;              //data  is  ready  
}mem_data_type;

endpackage

import cache_def::*;
`timescale 1ns/1ps

class rand_cl;
    rand bit [127:0] v;
endclass //rand_cl

module sim_mem(input bit clk,
               input  mem_req_type  req,
               output mem_data_type data);
        default clocking cb @(posedge clk);
        endclocking
 
        localparam MEM_DELAY = 100;

        bit [127:0] mem[*];
        rand_cl rand_data = new();

        always @(posedge clk) begin
              data.ready = '0;
              #1;
              if (!mem.exists(req.addr)) begin        //random initialize DRAM data on-demand 
                      rand_data.randomize();
                      mem[req.addr] = rand_data.v;     
              end

            //   #1;
              if (req.valid) begin
                $display("%t: [Memory] %s @ addr=%x with data=%x", $time, (req.rw) ? "Write" : "Read", req.addr, 
                        (req.rw) ? req.data : mem[req.addr]);
                ##MEM_DELAY;
                if (req.rw)
                        mem[req.addr] = req.data;
                else begin
                        data.data = mem[req.addr];             
                end

                $display("%t: [Memory] request finished", $time);
                data.ready = '1;                                
              end
        end 
endmodule

module tb_setassoc_cache ;
    bit clk;
    initial forever #2 clk = ~clk;

    mem_req_type    mem_req;        
    mem_data_type   mem_data;
    cpu_req_type     cpu_req;
    cpu_result_type  cpu_res;
    
    bit     rst;
    

    program sim_cpu;
     default clocking cb @(posedge clk);

     endclocking
     initial begin
        $timeformat(-9, 3, "ns", 10);

        rst = '0;
        ##5;
        rst = '1;
        ##10;
        rst = '0;

        cpu_req = '{default:0};

        //read clean miss 
        cpu_req.rw = '0;
        cpu_req.addr[INDEX_WIDTH+LINE_BYTE_OFFSET-1:LINE_BYTE_OFFSET] = 2;  //index 
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h1234;             //tag
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data);
        cpu_req.valid = '0;
        ##5;

        //read clean miss 
        cpu_req.rw = '0;
        cpu_req.addr[INDEX_WIDTH+LINE_BYTE_OFFSET-1:LINE_BYTE_OFFSET] = 2;  //index 
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h2345;             //tag
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data);
        cpu_req.valid = '0;
        ##5;

        //read clean miss 
        cpu_req.rw = '0;
        cpu_req.addr[INDEX_WIDTH+LINE_BYTE_OFFSET-1:LINE_BYTE_OFFSET] = 2;  //index 
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h3456;             //tag
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data);
        cpu_req.valid = '0;
        ##5;

        //read clean miss 
        cpu_req.rw = '0;
        cpu_req.addr[INDEX_WIDTH+LINE_BYTE_OFFSET-1:LINE_BYTE_OFFSET] = 2;  //index 
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h4567;             //tag
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data);
        cpu_req.valid = '0;
        ##5;

        //read hit clean line
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 8;                             //byte offset
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data); 
        cpu_req.valid = '0;
        ##5;

        //read hit clean line
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h3456;             //tag
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 8;                             //byte offset
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data); 
        cpu_req.valid = '0;
        ##5;

        //read hit clean line
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h2345;             //tag
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 8;                             //byte offset
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data); 
        cpu_req.valid = '0;
        ##5;
        
        //read hit clean line
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h1234;             //tag
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 8;                             //byte offset
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data); 
        cpu_req.valid = '0;
        ##5;

        //write hit clean line (cache line is dirty afterwards) (4byte)
        cpu_req.rw = '1;
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h1234;             //tag
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 'ha;
        if(DATA_WIDTH==64)begin
            cpu_req.data = 64'haabbccffdeadbeef;
            cpu_req.wstrb = 8'b11111111;
        end
        else begin
            cpu_req.data = 32'hdeadbeef;
            cpu_req.wstrb = 4'b1111;
        end
        cpu_req.valid = '1;
        $display("%t: [CPU] write addr=%x with data=%x wstrb=%b", $time, cpu_req.addr, cpu_req.data, cpu_req.wstrb);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] write done", $time); 
        cpu_req.valid = '0;
        ##5;

        //write hit clean line (cache line is dirty afterwards) (2byte)
        cpu_req.rw = '1;
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h2345;             //tag
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 'ha;
        if(DATA_WIDTH==64)begin
            cpu_req.data = 64'haabbccffdeadbeef;
            cpu_req.wstrb = 8'b0001111;
        end
        else begin
            cpu_req.data = 32'hdeadbeef;
            cpu_req.wstrb = 4'b0011;
        end
        cpu_req.valid = '1;
        $display("%t: [CPU] write addr=%x with data=%x wstrb=%b", $time, cpu_req.addr, cpu_req.data, cpu_req.wstrb);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] write done", $time); 
        cpu_req.valid = '0;
        ##5;

        //write hit clean line (cache line is dirty afterwards) (1byte)
        cpu_req.rw = '1;
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h3456;             //tag
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 'ha;
        if(DATA_WIDTH==64)begin
            cpu_req.data = 64'haabbccffdeadbeef;
            cpu_req.wstrb = 8'b00000011;
        end        
        else begin
            cpu_req.data = 32'hdeadbeef;
            cpu_req.wstrb = 4'b0001;
        end
        cpu_req.valid = '1;
        $display("%t: [CPU] write addr=%x with data=%x wstrb=%b", $time, cpu_req.addr, cpu_req.data, cpu_req.wstrb);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] write done", $time); 
        cpu_req.valid = '0;
        ##5;

        //write conflict miss (allocate, set cache line dirty) (4byte)
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h4321;               
        if(DATA_WIDTH==64)begin
            cpu_req.data = 64'hcafebeefcafebeef;
            cpu_req.wstrb = 8'b11111111;
        end  
        else begin
            cpu_req.data = 32'hcafebeef;
            cpu_req.wstrb = 4'b1111;
        end
        cpu_req.valid = '1;
        $display("%t: [CPU] write addr=%x with data=%x wstrb=%b", $time, cpu_req.addr, cpu_req.data, cpu_req.wstrb); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] write done", $time);
        cpu_req.valid = '0;
        ##5;

        //write conflict miss (write back then allocate, set cache line dirty) (2byte)
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h5432;               
        if(DATA_WIDTH==64)begin
            cpu_req.data = 64'hcafebeefcafebeef;
            cpu_req.wstrb = 8'b00001111;
        end          
        else begin
            cpu_req.data = 32'hcafebeef;
            cpu_req.wstrb = 4'b1100;
        end
        cpu_req.valid = '1;
        $display("%t: [CPU] write addr=%x with data=%x wstrb=%b", $time, cpu_req.addr, cpu_req.data, cpu_req.wstrb); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] write done", $time);
        cpu_req.valid = '0;
        ##5;

        //write conflict miss (write back then allocate, cache line dirty) (1byte)
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h6543;               
        if(DATA_WIDTH==64)begin
            cpu_req.data = 64'hcafebeefcafebeef;
            cpu_req.wstrb = 8'b00000011;
        end        
        else begin
            cpu_req.data = 32'hcafebeef;
            cpu_req.wstrb = 4'b1000;
        end
        cpu_req.valid = '1;
        $display("%t: [CPU] write addr=%x with data=%x wstrb=%b", $time, cpu_req.addr, cpu_req.data, cpu_req.wstrb); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] write done", $time);
        cpu_req.valid = '0;
        ##5;                

        //read hit dirty line
        cpu_req.rw = '0;
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = '0;
        cpu_req.valid = '1; 
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr);
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data); 
        cpu_req.valid = '0;
        ##5;

        //read conflict miss dirty line (write back then allocate, cache line is clean)  
        cpu_req.addr[31:INDEX_WIDTH+LINE_BYTE_OFFSET] = 'h5678;
        cpu_req.addr[LINE_BYTE_OFFSET-1:0] = 4;
        cpu_req.valid = '1;
        $display("%t: [CPU] read addr=%x", $time, cpu_req.addr); 
        wait(cpu_res.ready == '1);
        $display("%t: [CPU] get data=%x", $time, cpu_res.data); 
        cpu_req.valid = '0;
        ##5; 

        $finish();
     end
    endprogram

    cache_controller #(
        .DATA_WIDTH(DATA_WIDTH)
    )dm_cache_inst(
        .clk(clk),
        .rst(rst),
        .cpu_req_addr(cpu_req.addr),
        .cpu_req_wdata(cpu_req.data),
        .cpu_req_wstrb(cpu_req.wstrb),
        .cpu_req_rw(cpu_req.rw),
        .cpu_req_valid(cpu_req.valid),
        .cache_rsp_data(cpu_res.data),
        .cache_rsp_ready(cpu_res.ready),
        .cache_req_addr(mem_req.addr),
        .cache_req_data(mem_req.data),
        .cache_req_rw(mem_req.rw),
        .cache_req_valid(mem_req.valid),
        .mem_rsp_data(mem_data.data),
        .mem_rsp_ready(mem_data.ready)
    );

    sim_mem dram_inst(
        .*, 
        .req(mem_req), 
        .data(mem_data)
    );

endmodule